var App = (function(window, $) {

	var DEBUG = true;
	var NETWORK_ERROR = '网络忙，请您休息会！';
	var emptyFunction = function() {
	};

	var isNull = function(s) {
		return (typeof s === 'undefined' || s === null);
	};

	var log = function(s) {
		if (!DEBUG) {
			return;
		}
		var f = window.console;
		if (f && f.log) {
			f.log(s);
		}
	};

	/***************************************************************************
	 * 发起api请求，
	 * 
	 * @param path,
	 *            路径, 如/product/save
	 * @param data,
	 *            数据参数, 如{"key1":"value1","key2":"value2"}
	 * @param options,
	 *            可选参数，有success, error等
	 * 
	 **************************************************************************/
	var api = function(path, data, options) {
		var defer = $.Deferred();
		options = $.extend({
			type : 'post',
			preload : true,
			success : emptyFunction,
			error : function(err) {
				if (isNull(err)) {
					err = NETWORK_ERROR;
				}
				if (typeof err != 'string') {
					err = err.msg;
					if (!err) {
						err = NETWORK_ERROR;
					}
				} 
				alert(err);
			}
		}, options || {});

		var dataAsStr = null;
		{
			if (isNull(data)) {
				data = {};
			} else if (typeof data === 'string') {
				var dataArray = data.split('&');
				var dataAsMap = {};
				for (var i = 0; i < dataArray.length; i++) {
					var dataItem = dataArray[i].split('=');
					var value = isNull(dataItem[1]) ? dataItem[1] : '';
					dataAsMap[dataItem[0]] = decodeURIComponent(value);
				}
				data = dataAsMap;
			}
			var trimmedData = {};
			for ( var key in data) {
				var value = data[key];
				if (isNull(value)) {
					value = '';
				}
				trimmedData[key] = value;
			}
			data = trimmedData;
			if (!isNull(data)) {
				var s = '';
				for ( var key in data) {
					if (s.length > 0) {
						s += '&';
					}
					var v = data[key];
					if ($.isPlainObject(v) || $.isArray(v)) {
						v = JSON.stringify(v);
					}
					s += (key + '=' + encodeURIComponent(v));
				}
				dataAsStr = s;
			}
		}

		var onError = options.error;
		var onSuccess = options.success;

		var ajaxOptions = {
			url : path,
			type : options.type,
			data : dataAsStr,
			error : function(xhr) {
				if (options.preload) {
					$.preloader.hide();
				}
				onError();
				defer.reject();
			},
			success : function(obj) {
				if (options.preload) {
					$("#upIn").hide(); 
					$.preloader.hide();
				}
				if (isNull(obj) || isNull(obj.result)) {
					onError(obj ? obj.error : null);
					return;
				}
				var ret = onSuccess(obj.result);
				if (ret === false) {
					defer.reject();
				} else {
					defer.resolve(obj.result);
				}
			}
		};
		if (options.preload) { 
			$.preloader.show();
		}
		$.ajax($.extend(options, ajaxOptions));
		return defer.promise();
	};
	
	/*判断浏览器版本，若低于IE7则提示用户升级浏览器*/
	var updateWebview = (function(){
		 var browser=navigator.appName;
		 var b_version=navigator.appVersion;
		 var version=b_version.split(";");
		 if(version.length>2){
			 var trim_Version=version[1].replace(/[ ]/g,"");   
			 if(browser=="Microsoft Internet Explorer" && trim_Version=="MSIE6.0")  
			  	{  
				 	var ifdown = $.cookie("downExplorer");
				 	if(!ifdown){
				 		window.location.href="/demoHtml/update.html?url="+location.href;
				 	}
				}  /*else if( browser=="Microsoft Internet Explorer" && trim_Version=="MSIE7.0") {
					 window.location.href="/demoHtml/update.html"
				} */
		 }
	})();
	
	
	// var getPosition = function(element) {
	// var xPosition = 0;
	// var yPosition = 0;
	//
	// while (element) {
	// xPosition += (element.offsetLeft - element.scrollLeft +
	// element.clientLeft);
	// yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
	// element = element.offsetParent;
	// }
	// return {
	// x : xPosition,
	// y : yPosition
	// };
	// };

	var detectFixedPosition = function(element) {
		var e = element;
		while (e) {
			if ($(e).css('position') == 'fixed') {
				return 'fixed';
			}
			e = e.offsetParent;
		}
		return $(element).css('position');
	};


	var uploaderErrorMessage = function(errorCode){
		var msg = null;

		switch (errorCode){
			case -100 : msg = "抱歉，发生通用错误";break;
			case -200 : msg = "抱歉，http网络错误，请检查您的网络是否通畅";break;
			case -300 : msg = "抱歉，磁盘读写错误";break;
			case -400 : msg = "抱歉，因为安全问题而产生错误";break;
			case -500 : msg = "抱歉，初始化时发生错误";break;
			case -600 : msg = "抱歉，选择的文件太大！";break;
			case -601 : msg = "抱歉，选择的文件类型不符系统设定！";break;
			case -602 : msg = "抱歉，在不允许的情况下选择了重复的文件！";break;
			case -700 : msg = "抱歉，图片格式错误！";break;
			case -702 : msg = "抱歉，文件大小超过了处理范围！";break;
		}

		return msg;
	};


	var fileUploader = function(options) {
		options = $.extend({
			maxFiles : 1,
			preload : true,
			ifCompressImage : false,//是否压缩图片字段  默认是false
			compressSize : 600,//默认压缩尺寸是600px
			multiSelection : false,
			formData : function() {
				return {};
			},
			error : function(msg) {
				alert(msg || NETWORK_ERROR);
			},
			filters:{
			}
		}, options || {});
		if($.contains(options.url,"?")){
			options.url += options.ifCompressImage ? "&ifCompressImage=1&compressSize="+options.compressSize : "";
		}else{
			options.url += options.ifCompressImage ? "?ifCompressImage=1&compressSize="+options.compressSize : "";
		}
		//console.log(options.url);
		var uploader = new plupload.Uploader({
			runtimes : 'html5,flash',
			url : options.url,
			browse_button : options.id,
			container : document.body,
			max_file_size : options.maxFileSize,
			multi_selection : options.multiSelection,
			flash_swf_url : '/assets/vendor/plupload/Moxie.swf',
			filters:options.filters,
			init : {
				Init : function(up) {
					up.reposition();
				},
				FilesAdded : function(up, files) {
					var max = options.maxFiles;
					if (max >= 0 && up.files.length > max) {
						up.splice(max, up.files.length);
						files.splice(max, files.length);
						return false;
					}
					options.filesAdded && options.filesAdded(files, up);
					if (options.autoStart) {
						up.start();
					}
				},
				FileFiltered : function(up ,files){ 
					options.fileFiltered && options.fileFiltered(files, up);
				},
				BeforeUpload : function(up, file) { 
					if (options.preload) {
						$.preloader.show();
					}
					up.setOption('multipart_params', options.formData(up));
					options.beforeUpload && options.beforeUpload(file, up);
				},
				FileUploaded : function(up, file, res) {
					options.uploaded && options.uploaded(file, up);
					var data = null;
					try {
						eval("data = " + res.response);
					} catch (e) {
					}
					if (!data) {
						data = {};
					}
					if (data.result) {
						options.success
								&& options.success(data.result, file, up);
					} else {
						$(this.getOption('browse_button')).prevAll('input[type=text]').val('');
						var msg = data.error ? data.error.msg : null;
						if (!msg) {
							msg = NETWORK_ERROR;
						}
						options.error(msg, up);
					}
				},
				UploadComplete : function(up, files) {
					if (options.preload) { 
						$.preloader.hide();
					}
					//up.files = [];
					up.splice(0, up.files.length);
					files.splice(0, files.length);
					options.complete && options.complete(files, up);
				},
				Error : function(up, err) {
					console.log(err);
					if (!up.runtime) {
						var id = 'moxie-tip-container';
						if (document.getElementById(id) != null) {
							$('#' + id).css('display', 'block');
						} else {
							$('<div id="' + id + '"/>').appendTo(
									document.body).html(
									'很抱歉，当前浏览器不支持文件上传，请升级浏览器或<a href="https://get.adobe.com/flashplayer/?loc=cn" style="color:#336699;" target="_blank">安装Flash插件</a>');
						}
						return;
					}
					options.error(uploaderErrorMessage(err.code), up);
				}
			}
		});
		uploader.reposition = function() {
			var container = $('#' + this.getRUID() + '_container');
			if (!container[0]) {
				return;
			}
			var btn = $(this.getOption('browse_button'));
			var btnid = $('#'+btn.attr('id'));
			var positionType = detectFixedPosition(btnid[0]);
			if (positionType != 'fixed') {
				positionType = 'absolute';
			}
			var zindex = '10';
			if(btnid.parents('.selectfile').hasClass('selectfiledialog')){
			     zindex = '60000';
			}
			var pos = btnid.offset();
			if(typeof(pos)!='undefined'){
			if( pos.left == 0) {
				pos.left = -btnid.width();
			}

			//console.log('repoisition on: ' + btn.id);
			// JSON.stringify(pos) + ', ' + JSON.stringify(btn.position()));
			container.css('position', positionType);
			container.css({
				'left' : pos.left + 'px',
				'top' : pos.top + 'px',
				'width' : btnid.width() + 'px',
				'height' : btnid.height() + 'px',
				'z-index':zindex
			});
			
			if(btnid.parent().hasClass('uploadBox')){
				container.css('top',pos.top-($('body').scrollTop() || document.documentElement.scrollTop));
			}
		}
		};
		uploader.init();
		$('#' + options.id).onPositionChanged(function() {
			uploader.reposition();
		});
		$('#' + options.id).mouseover(function() {
			uploader.reposition();
		});
		return uploader;
	};

	var getScripts = function(data, options) {
		if (isNull(data) || data.length == 0) {
			return {
				html : ''
			};
		}
		var lines = data.split('\n');
		var scripts = [];
		var inScript = false;
		var scriptBuf = null;
		var p1 = new RegExp('<script.*?src=[\'"](.*?)[\'"].*?>.*</script>', "i");
		var p2 = new RegExp('<script.*?>', "i");
		var p3 = new RegExp('</script>', "i");
		var mts = null;
		var html = '';
		for (var i = 0; i < lines.length; i++) {
			var line = lines[i];
			var found = false;
			mts = p1.exec(line);
			if (mts) {
				scripts.push({
					type : 'script',
					url : mts[1]
				});
				found = true;
			}
			if (found) {
				continue;
			}
			if (!inScript) {
				mts = p2.exec(line);
				if (mts) {
					inScript = true;
					scriptBuf = '';
				} else {
					html += '\n';
					html += line;
				}
				continue;
			}
			mts = p3.exec(line);
			if (mts) {
				scripts.push({
					type : 'localscript',
					data : scriptBuf
				});
				inScript = false;
				scriptBuf = null;
			} else {
				scriptBuf += '\n';
				scriptBuf += line;
			}
		}
		return {
			html : html,
			scripts : scripts
		};
	};

	var _resourceCaches = {};

	var addURLParam = function(url, key, value) {
		return url + ((url.indexOf('?') > 0) ? '&' : '?') + key + '=' + value;
	};

	var wrapScriptURL = function(url, options) {
		if (!options.cache) {
			return addURLParam(url, '__PREVENT_FROM_CACHE__', new Date()
					.getTime());
		}
		if (options.version) {
			return addURLParam(url, '__VERSION__', options.version);
		}
		return url;
	};

	var loadCssOrScript = function(type, url, options) {
		var success = options.success;
		var done = false;
		if (_resourceCaches[url]) {
			if (success) {
				success();
			}
			return;
		}
		var res = null;
		if (type == 'script') {
			res = document.createElement('script');
		} else if (type == 'css') {
			res = document.createElement('link');
			res.setAttribute('rel', 'stylesheet');
			res.setAttribute('type', 'text/css');
		}
		res.onload = res.onreadystatechange = function() {
			if (!done
					&& (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
				done = true;
				_resourceCaches[url] = true;
				if (success) {
					success();
				}
			}
		};
		res.onerror = function() {
			options.error && options.error('Failed to load: ' + url);
		};
		var loadURL = wrapScriptURL(url, options);
		if (type == 'script') {
			res.setAttribute('src', loadURL);
		} else {
			res.setAttribute('href', loadURL);
		}
		document.getElementsByTagName('head')[0].appendChild(res);
	};
	var loadCss = function(url, options) {
		loadCssOrScript('css', url, options);
	};
	var loadScript = function(url, options) {
		loadCssOrScript('script', url, options);
	};

	var getResourceType = function(url) {
		var searchOffset = url.indexOf('?');
		if (searchOffset > 0) {
			url = url.substring(0, searchOffset);
		}
		var fileNameOffset = url.lastIndexOf('/');
		if (fileNameOffset > 0) {
			url = url.substring(fileNameOffset + 1);
		}
		var typeOffset = url.lastIndexOf('.');
		if (typeOffset <= 0) {
			return null;
		}
		return url.substring(typeOffset + 1).toLowerCase();
	};

	var loadResource = function(res, options) {
		var defer = $.Deferred();
		options = $.extend({
			cache : true
		}, options || {});
		var userSuccess = options.success;
		var userError = options.error;
		options.success = function() {
			var ok = true;
			if (userSuccess) {
				ok = userSuccess();
			}
			if (ok === false) {
				defer.reject();
			} else {
				defer.resolve();
			}
		};
		options.error = function(e) {
			userError && userError(e);
			defer.reject();
		};
		var url = null;
		var dataType = null;
		if (typeof res === 'string') {
			url = res;
			dataType = getResourceType(res);
		} else {
			dataType = res.type;
			if (!dataType) {
				dataType = res.dataType;
			}
			url = res.url;
		}
		if (!dataType) {
			dataType = options.dataType;
		}
		if (!dataType || dataType == 'js') {
			dataType = 'script';
		}
		if (dataType == 'script') {
			loadScript(url, options);
		} else if (dataType == 'css') {
			loadCss(url, options);
		} else if (dataType == 'localscript') {
			setTimeout(function() {
				var data = res.data;
				var err = null;
				try {
					if (window.eval) {
						window.eval(data);
					} else {
						window.execScript(data);
					}
				} catch (e) {
					err = 'Failed to execute script: ' + data;
					log(e);
					log(err);
				}
				if (err) {
					options.error(err);
				} else {
					options.success();
				}
			}, 0);
		} else {
			setTimeout(function() {
				options.error('Bad dataType: ' + dataType);
			});
		}
		return defer.promise();
	};

	var loadResources = function(urls, options) {
		options = options || {};
		var defer = $.Deferred();
		var userSuccess = options.success;
		var i = -1;
		var success = function() {
			i++;
			if (i < urls.length) {
				loadResource(urls[i], options);
			} else {
				var ok = true;
				if (userSuccess) {
					ok = userSuccess();
				}
				if (ok === false) {
					defer.reject();
				} else {
					defer.resolve();
				}
			}
		};
		options.success = success;
		setTimeout(success, 0);
		return defer.promise();
	};

	var dialog = (function() {
		var mask, dialog, body, preloader, options;
		var setHtml = function(html, scripts) {
			if (!dialog) {
				return;
			}
			body.html(html);
			var callback = function() {
				body.addClass('visible');
				if (scripts && scripts.length > 0) {
					loadResources(scripts, {
						success : options.success
					});
				} else if (options.success) {
					options.success();
				}
			};
			if (options.autoSize) {
				var b = body[0];
				var header = dialog.find('#dialog-header')[0];
				var w = Math.max(b.scrollWidth + 15, b.offsetWidth);
				var h = b.scrollHeight + header.scrollHeight;
				setDimension(w, h, callback);
			} else {
				setDimension(options.width, options.height, callback);
			}
		};
		var pageSize = function() {
			var w = (self && self.innerWidth)
					|| document.documentElement.clientWidth
					|| document.body.clientWidth;
			var h = (self && self.innerHeight)
					|| document.documentElement.clientHeight
					|| document.body.clientHeight;
			var size = {
				w : w,
				h : h
			};
			return size;
		};
		var setDimension = function(w, h, callback) {
			if (!dialog) {
				return;
			}
			if (w < options.minWidth) {
				w = options.minWidth;
			}
			if (h < options.minHeight) {
				h = options.minHeight;
			}
			var ps = pageSize();
			var left = parseInt((ps.w - w) / 2);
			if (left < 0) {
				left = 0;
			}
			var top = parseInt((ps.h - h) / 2);
			if (top < 0) {
				top = 0;
			}
			dialog.animate({
				width : w,
				height : h,
				left : left,
				top : top
			}, options.animateDuration, callback);
		};
		var open = function(varOptions) {
			close();
			options = $.extend({
				minWidth : 300,
				minHeight : 200,
				animateDuration : 200,
				autoSize : true,
				timeout : 30000,
				error : function() {
					alert('页面加载失败，请稍后重试');
					close();
				}
			}, varOptions || {});
			mask = $('<div id="dialog-mask"></div>');
			dialog = $('<div id="dialog"><div id="dialog-header"><span id="dialog-title">'
					+ options.title
					+ '</span><div id="dialog-close"></div></div></div>');
			body = $('<div id="dialog-body"></div>');
			preloader = $('<div id="dialog-preloader"></div>');
			$(document.body).append(mask).append(dialog);
			{
				var ps = pageSize();
				var left = parseInt((ps.w - dialog.outerWidth()) / 2);
				if (left < 0) {
					left = 0;
				}
				var top = parseInt((ps.h - dialog.outerHeight()) / 2);
				if (top < 0) {
					top = 0;
				}
				dialog.css('left', left + 'px');
				dialog.css('top', top + 'px');
				dialog.css('visibility', 'visible');
			}
			dialog.append(body).append(preloader);
			dialog.find('#dialog-close').click(close);
			var url = options.url;
			if (url) {
				if (url.indexOf('?') > 0) {
					url += '&';
				} else {
					url += '?';
				}
				url += '__PREVENT_CACHE_=' + new Date().getTime();
				$.ajax(url, {
					dataType : 'html',
					timeout : options.timeout,
					success : function(data, textStatus, xhr) {
						if (preloader) {
							preloader.remove();
						}
						var scriptsAndHtml = getScripts(data);
						setHtml(scriptsAndHtml.html, scriptsAndHtml.scripts);
					},
					error : function() {
						if (preloader) {
							preloader.remove();
						}
						options.error();
					}
				});
			} else {
				preloader.remove();
				setHtml(options.html);
			}
		};
		var close = function() {
			if (options && options.onClose) {
				options.onClose();
			}
			options = null;
			if (mask) {
				mask.remove();
				mask = null;
			}
			if (dialog) {
				dialog.remove();
				dialog = null;
			}
			preloader = null;
		};
		return {
			open : open,
			close : close
		}
	})();

	/***************************************************************************
	 * ps: 分页对象 {"offset":xx, "limit":xx", "total":xx} cb: 点击某一页后的回调函数 options:
	 * 参数选项
	 **************************************************************************/
	var pagination = function(ps, cb, options) {
		options = $.extend({
			container : '#pagination-wrap',
			edgeEntryNum : 5,
			displayEntryNum : 8,
			preText : '&lt;',
			nextText : '&gt;'
		}, options || {});

		var offset = ps.offset;
		var limit = ps.limit;
		var total = ps.total;

		$(options.container).pagination(total, {
			items_per_page : limit,
			current_page : offset / limit,
			num_edge_entries : options.edgeEntryNum,
			num_display_entries : options.displayEntryNum,
			prev_text : options.preText,
			next_text : options.nextText,
			callback : function(pageIndex) {
				cb && cb(pageIndex * limit, limit);
				return false;
			}
		});
	};

	var getParameters = function() {
		var query = window.location.search.substring(1);
		var vars = query.split("&");
		var pairs = {};
		for (var i = 0; i < vars.length; i++) {
			var pair = vars[i].split("=");
			var k = pair[0];
			var v = (typeof pair[1] === 'undefined') ? '' : pair[1];
			if (v.length > 0) {
				v = decodeURIComponent(v);
			}
			pairs[k] = v;
		}
		return pairs;
	};

	var getParameter = function(key) {
		var kv = getParameters();
		return kv[key];
	};
	
	var wrapParametersAsSearch = function(params) {
		var search = '';
		if (!params) {
			return search;
		}
		for (var key in params) {
			if (!key) {
				continue;
			}
			if (search.length > 0) {
				search += '&';
			}
			search += key + '=' + encodeURIComponent(params[key]);
		}
		return search;
	};

	var string = {
		isNull : function(s) {
			if (typeof s === 'undefined' || s === null) {
				return true;
			}
			return false;
		},
		isEmpty : function(s) {
			if (typeof s === 'undefined' || s === null) {
				return true;
			}
			if (typeof s !== 'string') {
				s = '' + s;
			}
			s = $.trim(s);
			return s.length == 0;
		},
		replaceAll : function(str, find, replace) {
			return str.replace(new RegExp(find, 'g'), replace);
		},
		uuid : function() {
			var t = '' + new Date().getTime();
			for (var i = 0; i < 3; i++) {
				t += '-' + parseInt(Math.random() * 1000000);
			}
			return t;
		}
	};

	var validateRequire = function(ctrl, v) {
		var require = ctrl.attr('data-validate-require');
		if (!require) {
			return null;
		}
		var valueType = $.type(v);
		var ok = true;
		if (valueType == 'undefined' || valueType == 'null') {
			ok = false;
		} else if (valueType == 'string') {
			if (string.isEmpty(v)) {
				ok = false;
			}
		}
		if (ok) {
			return null;
		}
		return '请完善信息: ' + ctrl.attr('data-validate-field');
	};

	var validateType = function(ctrl, v) {
		var vtype = ctrl.attr('data-validate-type');
		if (!vtype) {
			return null;
		}
		var fieldName = ctrl.attr('data-validate-field');
		if (vtype == 'int') {
			var ok = (v.indexOf('.') < 0) && (Math.floor(v) == v)
					&& $.isNumeric(v);
			if (!ok) {
				return fieldName + '需要是整数';
			}
		} else if (vtype == 'number') {
			if (!$.isNumeric(v)) {
				return fieldName + '需要是数字';
			}
		} else if (vtype == 'mobile') {
			if (!/^1[3|4|5|8|7][0-9]\d{8}$/.test(v)) {
				return fieldName + '需要是正确的手机号码';
			}
		}
		return null;
	};

	var validateMaxLength = function(ctrl, v) {
		var maxlength = ctrl.attr('maxlength') || ctrl.attr('data-maxlength');
		if (!maxlength) {
			return null;
		}
		maxlength = parseInt(maxlength);
		var len = ('' + v).length;
		var vtype = ctrl.attr('data-validate-type');
		var fieldName = ctrl.attr('data-validate-field');
		if (vtype == 'number' || vtype == 'int') {
			var num = parseInt(v);
			if (num < 0) {
				len -= 1;
			}
			if (len > maxlength) {
				return fieldName + '数字太大了';
			}
		} else {
			if (len > maxlength) {
				return fieldName + '最多' + maxlength + '个字';
			}
		}
		return null;
	};

	var validateForm = function(form, data) {
		var ctrls = form.find('[data-validate-field]:visible');
		var ok = true;
		for (var i = 0; i < ctrls.length; i++) {
			var ctrl = $(ctrls[i]);
			var name = ctrl.attr('name');
			var v = data[name];
			var tip = validateRequire(ctrl, v);
			if (!tip) {
				tip = validateType(ctrl, v);
				if (!tip) {
					tip = validateMaxLength(ctrl, v);
				}
			}
			if (tip) {
				// var vctrl = $('#form-validatemsg-' + name);
				// if (vctrl[0]) {
				// vctrl.addClass('form-validatemsg-visible');
				// } else {
				alert(tip);
				// }
				ok = false;
				ctrl.focus();
				break;
			}
		}
		return ok;
	};

	var getContext = function(key, context) {
		if (isNull(context) || isNull(key)) {
			return null;
		}
		if (typeof key != 'string') {
			key = '' + key;
		}
		if (key.length == 0) {
			return null;
		}
		var keys = key.split('.');
		for (var i = 0; i < keys.length; i++) {
			var k = keys[i];
			if (!$.isPlainObject(context)) {
				return null;
			}
			context = context[k];
			if (isNull(context)) {
				return null;
			}
		}
		return context;
	};

	var template = function(s, context, options) {
		if (isNull(s)) {
			return null;
		}
		if (typeof s !== 'string') {
			s = '' + s;
		}
		context = context || {};
		options = options || {};
		var pattern = options.pattern || {
			'start' : '{{',
			'end' : '}}'
		};
		var startChars = pattern.start;
		var endChars = pattern.end;
		var forceNotNull = options.forceNotNull;
		var retainKeyIfNull = options.retainKeyIfNull;
		var buf = '';
		var fromIndex = 0;
		var dollarIndex = 0;
		var len = s.length;
		while ((dollarIndex = s.indexOf(startChars, fromIndex)) >= 0) {
			var endIndex = s.indexOf(endChars, fromIndex);
			if (endIndex <= 0) {
				throw 'Bad template: ' + s;
			}
			var key = s.substring(dollarIndex + startChars.length, endIndex);
			var value = getContext(key, context);
			if (isNull(value)) {
				if (forceNotNull) {
					throw 'No value defined for key: ' + key;
				}
				if (retainKeyIfNull) {
					value = startChars + key + endChars;
				} else {
					value = '';
				}
			}
			if (dollarIndex > fromIndex) {
				buf += s.substring(fromIndex, dollarIndex);
			}
			buf += value;
			fromIndex = endIndex + endChars.length;
		}
		if (fromIndex < len) {
			if (fromIndex == 0) {
				return s;
			}
			buf += s.substring(fromIndex);
		}
		return buf;
	};
	
	//处理小数乘法
	var accMul= function(arg1,arg2){
		var m=0,s1=arg1.toString(),s2=arg2.toString();
		try{m+=s1.split(".")[1].length}catch(e){}
		try{m+=s2.split(".")[1].length}catch(e){}
		return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m)
	};
	//处理小数加法
	var accAdd= function(arg1,arg2){
		var r1,r2,m;
		try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}
		try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}
		m=Math.pow(10,Math.max(r1,r2))
		return (arg1*m+arg2*m)/m
	};

	//2016/12/8  张栋
	var accMinus =function (arg1,arg2){
		var r1,r2,m;
		try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}
		try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}
		m=Math.pow(10,Math.max(r1,r2))
		return (accMul(arg1,m)-accMul(arg2,m))/m
	}
	
	var date = (function() {
		var pad = function(n) {
			return n < 10 ? '0' + n : n;
		};

		var format = function(date, fmt) {
			if (!date || !fmt) {
				return null;
			}
			var o = {
				"M+" : date.getMonth() + 1, // 月份
				"d+" : date.getDate(), // 日
				"h+" : date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, // 小时
				"H+" : date.getHours(), // 小时
				"m+" : date.getMinutes(), // 分
				"s+" : date.getSeconds(), // 秒
				"q+" : Math.floor((date.getMonth() + 3) / 3), // 季度
				"S" : date.getMilliseconds()
			};
			var week = {
				"0" : "\u65e5",
				"1" : "\u4e00",
				"2" : "\u4e8c",
				"3" : "\u4e09",
				"4" : "\u56db",
				"5" : "\u4e94",
				"6" : "\u516d"
			};
			if (/(y+)/.test(fmt)) {
				fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "")
						.substr(4 - RegExp.$1.length));
			}
			if (/(E+)/.test(fmt)) {
				fmt = fmt
						.replace(
								RegExp.$1,
								((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "\u661f\u671f"
										: "\u5468")
										: "")
										+ week[date.getDay() + ""]);
			}
			for ( var k in o) {
				if (new RegExp("(" + k + ")").test(fmt)) {
					fmt = fmt.replace(RegExp.$1,
							(RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k])
									.substr(("" + o[k]).length)));
				}
			}
			return fmt;
		};

		var formatISO8601 = function(d) {
			if (!d) {
				return null;
			}
			return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-'
					+ pad(d.getUTCDate()) + 'T' + pad(d.getUTCHours()) + ':'
					+ pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds())
					+ 'Z';
		};
		var getInt = function(s) {
			var offset = 0;
			for (var i = 0; i < s.length; i++) {
				if (s.charAt(i) == '0') {
					continue;
				}
				offset = i;
				break;
			}
			if (offset == 0) {
				return parseInt(s);
			}
			return parseInt(s.substr(offset));
		};
		var parse = function(v, timezoneOffset) {
			if (!v) {
				return null;
			}
			// yyyy-MM-ddTHH:mm:ssZ
			// yyyy-MM-ddTHH:mm:ss.SSSZ
			// yyyy-MM-dd HH:mm:ss.SSS
			var index = 0;
			var year = getInt(v.substr(index, 4));
			index += 5;
			var month = getInt(v.substr(index, 2)) - 1;
			index += 3;
			var day = getInt(v.substr(index, 2));
			index += 3;
			var hour = index >= v.length ? 0 : getInt(v.substr(index, 2));
			index += 3;
			var minute = index >= v.length ? 0 : getInt(v.substr(index, 2));
			index += 3;
			var second = index >= v.length ? 0 : getInt(v.substr(index, 2));
			// TODO more format
			if (v.charAt(v.length - 1) == 'Z') {
				var millSecond = v.indexOf('.') > 0 ? getInt(v.substring(v
						.indexOf('.') + 1, v.length - 1)) : 0;
				var d = new Date();
				d.setUTCFullYear(year);
				d.setUTCMonth(month);
				d.setUTCDate(day);
				d.setUTCHours(hour);
				d.setUTCMinutes(minute);
				d.setUTCSeconds(second);
				d.setUTCMilliseconds(millSecond);
				return d;
			} else {
				var millSecond = v.indexOf('.') > 0 ? getInt(v.substring(v
						.indexOf('.') + 1)) : 0;
				var date = new Date(year, month, day, hour, minute, second,
						millSecond);
				if (!isNull(timezoneOffset)) {
					var diff = timezoneOffset - date.getTimezoneOffset();
					date.setTime(date.getTime() - diff * 60 * 1000);
				}
				return date;
			}
		};

		return {
			parse : parse,
			format : format,
			formatISO8601 : formatISO8601
		};
	})();

	return {
		isNull : isNull,
		string : string,
		date : date,
		getParameters : getParameters,
		getParameter : getParameter,
		wrapParametersAsSearch: wrapParametersAsSearch,
		template : template,

		networkError : NETWORK_ERROR,
		api : api,
		fileUploader : fileUploader,
		dialog : dialog,
		pagination : pagination,
		validateForm : validateForm,
		accMul : accMul,
		accAdd : accAdd,
		accMinus:accMinus
	};
})(window, jQuery);